﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Data;
using OverStore.Runtime.Logger;

namespace OverStore.Runtime.Storage
{
    /// <summary>
    /// Defines how the results of two <see cref="AdoNetCommandBase"/> classes are merged into one result due executing <see cref="AdoNetScript"/>.
    /// </summary>
    public enum ResultComposition
    {
        /// <summary>
        /// Merge two results into single. If two results contains result with the same name, the 
        /// result from FIRST command's result is used.
        /// </summary>
        MergePreferFirst,
        /// <summary>
        /// Merge two results into single. If two results contains result with the same name, the 
        /// result from SECOND command's result is used.
        /// </summary>
        MergePreferSecond
    }

    /// <summary>
    /// Sequence of two <see cref="AdoNetCommandBase"/> commands. Because <see cref="AdoNetScript"/> class
    /// is also <see cref="AdoNetCommandBase"/> you may compose complex scripts from set of simple.
    /// </summary>
    public sealed class AdoNetScript : AdoNetCommandBase
    {
        private readonly AdoNetCommandBase _First;
        private readonly AdoNetCommandBase _Second;
        private readonly ResultComposition _ResultComposition;

        /// <summary>
        /// Initializes a new instance of the <see cref="AdoNetScript"/> class.
        /// </summary>
        /// <param name="first">The first command.</param>
        /// <param name="second">The second command.</param>
        /// <param name="resultComposition">Defines how the results of first and second commands are composed into one which will be result of the AdoNetScript command.</param>
        public AdoNetScript(AdoNetCommandBase first, AdoNetCommandBase second, ResultComposition resultComposition)
        {
            if (first == null)
                throw new ArgumentNullException("first");
            _First = first;
            if (second == null)
                throw new ArgumentNullException("second");
            _Second = second;
            _ResultComposition = resultComposition;
        }

        /// <summary>
        /// Executes current command using specified <see cref="IDbCommand"/> and read all data from resultset returned.
        /// </summary>
        public override IEnumerable<TypedRecord> ReadAll(Func<AdoNetCommandBase, DataCommandContainer> createCommand, IRecord parameters)
        {
            if (createCommand == null)
                throw new ArgumentNullException("createCommand");
            if (parameters == null)
                throw new ArgumentNullException("parameters");
            return _First.ReadAll(createCommand, parameters)
                         .Concat(_Second.ReadAll(createCommand, parameters));
        }

        /// <summary>
        /// Executes specified command and return command result.
        /// </summary>
        public override TypedRecord Execute(Func<AdoNetCommandBase, DataCommandContainer> createCommand, IRecord parameters, out int rowsAffected)
        {
            if (createCommand == null)
                throw new ArgumentNullException("createCommand");
            if (parameters == null)
                throw new ArgumentNullException("parameters");

            try
            {
                int rowsAffectedFirst;
                var first = _First.Execute(createCommand, parameters, out rowsAffectedFirst);
                int rowsAffectedSecond;
                var second = _Second.Execute(createCommand, parameters, out rowsAffectedSecond);

                rowsAffected = rowsAffectedFirst + rowsAffectedSecond;


                if (_ResultComposition == ResultComposition.MergePreferFirst)
                    return new CompositeRecord(first, second);
                if (_ResultComposition == ResultComposition.MergePreferSecond)
                    return new CompositeRecord(second, first);
                throw new NotSupportedException("Unknown ResultComposition value.");
            }
            catch (PersistingRuntimeException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new PersistingRuntimeException("Ado.Net Script error: executing script failed.", ex);
            }
        }
    }
}
